import _ from 'lodash';

export function fitLine(points, method) {
  switch (method) {
    case 'LS':
      return fitLineLS(points);
    case 'PCA':
      return fitLinePCA(points);
    case 'WeightedPCA':
      return fitLineWeightedPCA(points);
    case 'IterativeReweightedPCA':
      return fitLineIterativeReweightedPCA(points);
  }
}

export function fitLineLS(points) {
  let sum_x = 0;
  let sum_y = 0;
  for (let [x, y] of points) {
    sum_x += x;
    sum_y += y;
  }
  let mean_x = sum_x / points.length;
  let mean_y = sum_y / points.length;

  let dx = points.map(([x, _]) => x - mean_x);
  let dy = points.map(([_, y]) => y - mean_y);

  let a =
    _.sum(_.zip(dx, dy).map(([x, y]) => x * y)) / _.sum(dx.map((x) => x * x));
  let b = mean_y - a * mean_x;

  return [a, -1, b];
}

export function fitLinePCA(points) {
  let sum_x = 0;
  let sum_y = 0;
  for (let [x, y] of points) {
    sum_x += x;
    sum_y += y;
  }
  let mean_x = sum_x / points.length;
  let mean_y = sum_y / points.length;

  let dx = points.map(([x, _]) => x - mean_x);
  let dy = points.map(([_, y]) => y - mean_y);

  let sxx = _.sum(dx.map((x) => x * x)) / (points.length - 1);
  let syy = _.sum(dy.map((y) => y * y)) / (points.length - 1);
  let sxy = _.sum(_.zip(dx, dy).map(([x, y]) => x * y)) / (points.length - 1);

  let a = sxx - syy - Math.sqrt((sxx - syy) * (sxx - syy) + 4.0 * sxy * sxy);
  let b = 2.0 * sxy;
  let c = -(a * mean_x + b * mean_y);
  let d = Math.hypot(a, b);
  let sigma =
    0.5 * (sxx + syy - Math.sqrt((sxx - syy) * (sxx - syy) + 4.0 * sxy * sxy));
  console.log('sigma:', sigma);

  return [a / d, b / d, c / d, sigma];
}

export function fitLineWeightedPCA(points) {
  let sum_x = 0;
  let sum_y = 0;
  let sum_w = 0;
  for (let [x, y, weight] of points) {
    sum_x += weight * x;
    sum_y += weight * y;
    sum_w += weight;
  }
  let mean_x = sum_x / sum_w;
  let mean_y = sum_y / sum_w;

  let dx = points.map(([x, y]) => x - mean_x);
  let dy = points.map(([x, y]) => y - mean_y);
  let ws = points.map(([x, y, w]) => w);

  let sxx =
    _.sum(_.zip(dx, ws).map(([x, w]) => w * x * x)) / (points.length - 1);
  let syy =
    _.sum(_.zip(dy, ws).map(([y, w]) => w * y * y)) / (points.length - 1);
  let sxy =
    _.sum(_.zip(dx, dy, ws).map(([x, y, w]) => w * x * y)) /
    (points.length - 1);

  let a = sxx - syy - Math.sqrt((sxx - syy) * (sxx - syy) + 4.0 * sxy * sxy);
  let b = 2.0 * sxy;
  let c = -(a * mean_x + b * mean_y);

  return [a, b, c];
}

export function fitLineIterativeReweightedPCA(points) {
  var line = fitLinePCA(points);
  let m = _.last(line);
  while (true) {
    let [a, b, c, s] = line;
    let sum_x = 0;
    let sum_y = 0;
    let ws = [];
    for (let [x, y] of points) {
      let d = a * x + b * y + c;
      let weight = 1.0 / Math.max(d * d, m);
      sum_x += weight * x;
      sum_y += weight * y;
      ws.push(weight);
    }
    let sum_w = _.sum(ws);
    let mean_x = sum_x / sum_w;
    let mean_y = sum_y / sum_w;

    let dx = points.map(([x, y]) => x - mean_x);
    let dy = points.map(([x, y]) => y - mean_y);

    let sxx =
      _.sum(_.zip(dx, ws).map(([x, w]) => w * x * x)) / (points.length - 1);
    let syy =
      _.sum(_.zip(dy, ws).map(([y, w]) => w * y * y)) / (points.length - 1);
    let sxy =
      _.sum(_.zip(dx, dy, ws).map(([x, y, w]) => w * x * y)) /
      (points.length - 1);

    let na = sxx - syy - Math.sqrt((sxx - syy) * (sxx - syy) + 4.0 * sxy * sxy);
    let nb = 2.0 * sxy;
    let nc = -(na * mean_x + nb * mean_y);
    let nd = Math.hypot(na, nb);
    let sigma =
      0.5 *
      (sxx + syy - Math.sqrt((sxx - syy) * (sxx - syy) + 4.0 * sxy * sxy));

    line = [na / nd, nb / nd, nc / nd, sigma];
    console.log('sigma:', sigma);
    if (sigma > s) {
      line = [a, b, c, s];
      break;
    }
  }
  return line;
}

export function findIntersection(line, rectangle) {
  let [a, b, c] = line;
  let [x, y, w, h] = rectangle;
  let points = [];

  let x0 = -(b * y + c) / a;
  if (x0 >= x && x0 <= x + w) {
    points.push([x0, y]);
  }

  let y0 = -(a * x + c) / b;
  if (y0 >= y && y0 <= y + h) {
    points.push([x, y0]);
  }

  let x1 = -(b * (y + h) + c) / a;
  if (x1 >= x && x1 <= x + w) {
    points.push([x1, y + h]);
  }

  let y1 = -(a * (x + w) + c) / b;
  if (y1 >= y && y1 <= y + h) {
    points.push([x + w, y1]);
  }

  return points;
}
